// ==UserScript== // @name 宝可梦点击(Poke Clicker)内核汉化脚本 // @namespace PokeClickerHelper // @version 0.10.19-f // @description 采用内核汉化形式,目前汉化范围:所有任务线、城镇名、NPC及对话 // @author DreamNya, ICEYe, iktsuarpok, 我是f谁?, 顶不住了, 银☆星, TerVoid // @match http://localhost:3000/ // @match https://www.pokeclicker.com // @match https://g8hh.github.io/pokeclicker/ // @match https://pokeclicker.g8hh.com // @match https://pokeclicker.g8hh.com.cn/ // @match https://yx.g8hh.com/pokeclicker/ // @match https://dreamnya.github.io/pokeclicker/ // @icon https://scriptcat.org/api/v2/resource/image/Y3VU6C1i3QnlBewG // @grant none // @run-at document-end // @license MIT // @connect cdn.jsdelivr.net // @connect raw.githubusercontent.com // ==/UserScript== /* global TownList, QuestLine:true, Notifier, MultipleQuestsQuest, App, NPC, NPCController */ //储存汉化文本 const Translation = {}; const TranslationHelper = { Translation, exporting: false }; const CoreModule = window.PokeClickerHelper ?? window.PokeClickerHelperPlus; (CoreModule ?? window).TranslationHelper = TranslationHelper; TranslationHelper.config = { CDN: CoreModule?.get("TranslationHelperCDN", "jsDelivr", true) ?? "jsDelivr", UpdateDelay: CoreModule?.get("TranslationHelperUpdateDelay", 3, true) ?? 3, Timeout: CoreModule?.get("TranslationHelperTimeout", 10000, true) ?? 10000, }; // 引用外部资源 // CDN-jsDelivr: https://cdn.jsdelivr.net // CDN-GitHub: https://raw.githubusercontent.com // GIT: https://github.com/DreamNya/PokeClickerHelper-Translation const CDN = { jsDelivr: "https://cdn.jsdelivr.net/gh/DreamNya/PokeClickerHelper-Translation/json/", GitHub: "https://raw.githubusercontent.com/DreamNya/PokeClickerHelper-Translation/main/json/", }; const resources = ["QuestLine", "Town", "NPC"]; const now = Date.now(); const failed = []; for (const resource of resources) { Translation[resource] = await FetchResource(resource).catch(() => { const cache = localStorage.getItem(`PokeClickerHelper-Translation-${resource}`); if (cache) { console.log("PokeClickerHelper-Translation", "fallback获取json", resource); return JSON.parse(cache); } else { console.log("PokeClickerHelper-Translation", "all failed获取json", resource); failed.push(resource); Notifier.notify({ title: "宝可梦点击(Poke Clicker)内核汉化脚本", message: `请求汉化json失败,请检查网络链接或更新脚本\n无法完成汉化:${resource}`, timeout: 6000000, }); return {}; } }); } async function FetchResource(resource, force = false) { const past = +(localStorage.getItem(`PokeClickerHelper-Translation-${resource}-lastModified`) ?? 0); if ( !force && (TranslationHelper.config.UpdateDelay < 0 || now - past <= 86400 * 1000 * TranslationHelper.config.UpdateDelay) ) { const cache = localStorage.getItem(`PokeClickerHelper-Translation-${resource}`); if (cache) { console.log("PokeClickerHelper-Translation", "从存储获取json", resource); return JSON.parse(cache); } } const url = `${CDN[TranslationHelper.config.CDN]}${resource}.json`; const response = await fetch(url, { cache: "no-store", // 超时中断 signal: AbortSignal.timeout(+TranslationHelper.config.Timeout || 10000), }); if (response.status == 200) { const json = await response.json(); console.log("PokeClickerHelper-Translation", "从CDN获取json", resource); localStorage.setItem(`PokeClickerHelper-Translation-${resource}`, JSON.stringify(json)); localStorage.setItem(`PokeClickerHelper-Translation-${resource}-lastModified`, now); return json; } else { throw new Error(); } } Translation.NPCName = Translation.NPC.NPCName ?? {}; Translation.NPCDialog = Translation.NPC.NPCDialog ?? {}; TranslationHelper.toggleRaw = false; // 汉化城镇 Object.values(TownList).forEach((t) => { const name = Translation.Town[t.name]; t.displayName = name ?? t.name; }); // 修改城镇文本显示绑定 $('[data-bind="text: player.town().name"]').attr("data-bind", "text: player.town().displayName"); $("[data-town]").each(function () { const name = $(this).attr("data-town"); $(this).attr("data-town", Translation.Town[name] || name); }); // 汉化任务线 QuestLine.prototype.realAddQuest = QuestLine.prototype.addQuest; QuestLine.prototype.addQuest = new Proxy(QuestLine.prototype.realAddQuest, { apply(target, questline, [quest]) { const name = questline.name; const translation = Translation.QuestLine[name]; if (translation) { const description = quest.description; const displayDescription = translation.descriptions[description]; if (displayDescription) { Object.defineProperty(quest, "description", { get: () => (TranslationHelper.exporting ? description : displayDescription), }); } if (quest instanceof MultipleQuestsQuest) { quest.quests.forEach((q) => { const description = q.description; const displayDescription = translation.descriptions[description]; if (displayDescription) { Object.defineProperty(q, "description", { get: () => (TranslationHelper.exporting ? description : displayDescription), }); } }); } } return Reflect.apply(target, questline, [quest]); }, }); window.realQuestLine = QuestLine; QuestLine = new Proxy(window.realQuestLine, { construct(...args) { const questline = Reflect.construct(...args); const { name, description } = questline; const translation = Translation.QuestLine[name]; const displayName = translation?.name; const displayDescription = translation?.description[description]; Object.defineProperty(questline, "displayName", { get: () => (TranslationHelper.exporting ? name : displayName ?? name), }); if (displayDescription) { Object.defineProperty(questline, "description", { get: () => (TranslationHelper.exporting ? description : displayDescription), }); } return questline; }, }); // 修改任务线文本显示绑定 document.querySelector( "#questDisplayContainer > div.questDisplayBlock.questLine > div.card-header > knockout[data-bind='text: $data.name']" ).dataset.bind = "text: $data.displayName"; document.querySelector("#bulletinBoardModal div.modal-body h5[data-bind='text: $data.name']").dataset.bind = "text: $data.displayName"; document .querySelectorAll('#questsModalQuestLinesPane knockout.font-weight-bold.d-block[data-bind="text: $data.name"]') .forEach((i) => (i.dataset.bind = "text: $data.displayName")); // 汉化NPC Object.values(TownList) .flatMap((i) => i.npcs) .forEach((npc) => { if (!npc) { return; } npc.displayName = Translation.NPCName[npc.name] ?? npc.name; npc.rawDialog = npc.dialog; npc.translatedDialog = npc.rawDialog?.map((d) => Translation.NPCDialog[d] ?? d); delete npc.dialog; }); Object.defineProperty(NPC.prototype, "dialog", { get() { return TranslationHelper.toggleRaw ? this.rawDialog : this.translatedDialog; }, }); // 修改NPC文本显示绑定 document.querySelector( "#townView button[data-bind='text: $data.name, click: () => NPCController.openDialog($data)']" ).dataset.bind = "text: $data.displayName, click: () => NPCController.openDialog($data)"; document.querySelector("#npc-modal h5").dataset.bind = "text: $data.displayName"; // 导出完整json方法 TranslationHelper.ExportTranslation = {}; TranslationHelper.ExportTranslation.QuestLine = function () { TranslationHelper.exporting = true; const json = App.game.quests.questLines().reduce((obj, questline) => { const { name, description } = questline; const translation = Translation.QuestLine[name]; const subObj = {}; subObj.name = translation.name ?? name; subObj.description = { [description]: translation.description[description] ?? "" }; subObj.descriptions = questline.quests().reduce((d, q) => { d[q.description] = translation.descriptions[q.description] ?? ""; if (q instanceof MultipleQuestsQuest) { q.quests.forEach((qq) => { d[qq.description] = translation.descriptions[qq.description] ?? ""; }); } return d; }, {}); obj[name] = subObj; return obj; }, {}); TranslationHelper.exporting = false; return json; }; TranslationHelper.ExportTranslation.NPC_format = function () { const toggleRaw = TranslationHelper.toggleRaw; TranslationHelper.toggleRaw = true; const json = Object.values(TownList).reduce((obj, town) => { const npcs = town.npcs; if (npcs?.length > 0) { obj[town.name] = npcs.map((npc) => { const subObj = { name: { [npc.name]: Translation.NPCName[npc.name] ?? "" }, }; if (npc.dialog?.length > 0) { subObj.dialog = Object.fromEntries(npc.dialog.map((d) => [d, Translation.NPCDialog[d] ?? ""])); } return subObj; }); } return obj; }, {}); TranslationHelper.toggleRaw = toggleRaw; return json; }; // UI (需要PokeClickerHelper) if (CoreModule) { const prefix = CoreModule.UIContainerID[0].replace("#", "").replace("Container", "") + "TranslationHelper"; CoreModule.UIDOM.push(`
测试结果:未测试
`); CoreModule.UIlistener.push(() => { $(`#${prefix}`) .on("click", `#${prefix}Refresh`, function () { this.disabled = true; window.PCH_ForceRefreshTranslation(false); }) .on("click", `#${prefix}Toggle`, function () { if (this.value == "切换原文") { $(this).text((this.value = "切换汉化")); TranslationHelper.toggleRaw = true; } else { $(this).text((this.value = "切换原文")); TranslationHelper.toggleRaw = false; } if ($("#npc-modal").is(":visible")) { NPCController.selectedNPC(NPCController.selectedNPC()); } }) .on("change", "[data-save=global]", function () { const id = this.id.replace(prefix, ""); if (this.value != "") { TranslationHelper.config[id] = this.value; } }) .on("click", `#${prefix}Test`, async function () { this.disabled = true; const start = Date.now(); const result = await FetchResource("Town", true) .then(() => { const now = Date.now(); return `测试结果:成功
${now - start}ms`; }) .catch(() => { return `测试结果:超时
不够科学`; }); this.disabled = false; $(`#${prefix}TestResult`).html(result); }); }); } if (failed.length == 0) { window.PCH_ForceRefreshTranslation = (refresh = true) => { resources.forEach((resource) => localStorage.removeItem(`PokeClickerHelper-Translation-${resource}-lastModified`)); refresh && location.reload(); }; Notifier.notify({ title: "宝可梦点击(Poke Clicker)内核汉化脚本", message: `汉化加载完毕\n可以正常加载存档\n\n`, timeout: 15000, }); }